package bancosys.tec.rtm.impl.diagnostician;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;

import bancosys.tec.rtm.diagnostician.AbstractDiagnostician;
import bancosys.tec.rtm.diagnostician.DiagnosisBean;

/**
 * {@link Diagnostician} que detecta deadlocks de threads
 * 
 * @author takeshi
 */
public class ThreadDeadLockDiagnostician extends AbstractDiagnostician {

    private final ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();

    private boolean jdk6 = true;

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return "Detector de DeadLocks";
    }

    /**
     * @param deadLocked thread.
     * @param bean bean.
     * @throws Exception em caso de erro.
     */
    private void fillBean(Thread[] deadLocked, DiagnosisBean bean) throws Exception {
        for (Thread thread : deadLocked) {
            ThreadInfo info = this.mxBean.getThreadInfo(thread.getId(), Integer.MAX_VALUE);
            bean.addDescription("--> " + thread.toString() + " <-- \n");
            addOwningLocks(info, bean);
            if (this.jdk6) {
                addLockInfo(info, bean);
            }
            bean.addDescription("\n------------ end of thread ------------- \n");
        }
    }

    /**
     * @param info the {@link ThreadInfo}
     * @param bean {@link DiagnosisBean}
     * @throws Exception e
     */
    private void addLockInfo(ThreadInfo info, DiagnosisBean bean) throws Exception {
        Object obj = info.getClass().getMethod("getLockInfo").invoke(info);
        bean.addDescription("waiting for lock: " + obj);
    }

    /**
     * @param info {@link ThreadInfo}
     * @param bean {@link DiagnosisBean}
     * @throws Exception e
     */
    private void addOwningLocks(ThreadInfo info, DiagnosisBean bean) throws Exception {
        if (this.jdk6) {
            bean.addDescription("owning locks: \n");
            Object[] lockInfos = (Object[]) info.getClass().getMethod("getLockedSynchronizers").invoke(info);
            for (Object lockInfo : lockInfos) {
                bean.addDescription("-> " + lockInfo);
            }
        }
    }

    /**
     * @return long[]
     */
    private long[] deadlockedThreads() {
        if (this.jdk6) {
            try {
                Method method = this.mxBean.getClass().getMethod("findDeadlockedThreads");
                if (method != null) {
                    return (long[]) method.invoke(this.mxBean);
                }
            } catch (SecurityException e) {
                this.jdk6 = false;
            } catch (NoSuchMethodException e) {
                this.jdk6 = false;
            } catch (IllegalArgumentException e) {
                this.jdk6 = false;
            } catch (IllegalAccessException e) {
                this.jdk6 = false;
            } catch (InvocationTargetException e) {
                this.jdk6 = false;
            }
        }
        return this.mxBean.findMonitorDeadlockedThreads();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean fillDiagnosisBean(DiagnosisBean bean) throws Exception {
        long[] deadlockedThreads = this.deadlockedThreads();
        if (deadlockedThreads == null) {
            bean.addDescription("Could not get deadlocked threads list");
            return false;
        } else if (deadlockedThreads.length > 0) {
            Set<Thread> keySet = Thread.getAllStackTraces().keySet();
            Thread[] deadLocked = new Thread[deadlockedThreads.length];
            int j = 0;
            for (int i = 0; i < deadlockedThreads.length; i++) {
                long l = deadlockedThreads[i];
                for (Thread thread : keySet) {
                    if (l == thread.getId()) {
                        deadLocked[j++] = thread;
                        break;
                    }
                }
            }
            this.fillBean(deadLocked, bean);
        } else {
            bean.addDescription("no deadlock detected");
        }
        return true;
    }

}
